<?php

namespace services\rbac;

use api\modules\v1\forms\rbac\MenuForm;
use common\components\Service;
use common\helpers\DateHelper;
use common\helpers\RedisHelper;
use common\models\member\Member;
use common\models\rbac\Menu;
use common\models\rbac\RolePermission;
use services\log\LogMemberOpService;
use services\member\MemberService;
use Yii;

/**
 * Class MenuService
 * @package services\rbac
 */
class MenuService extends Service
{
    /**
     * 菜单列表
     * @param array $params
     * @param int $page
     * @param int $pageSize
     * @return array
     */
    public function getListPage(array $params = [], int $page = 0, int $pageSize = 0): array
    {
        $query = Menu::find()->alias('a')
            ->select([
                'a.*',
//                'b.name AS module_name',
                'c.name pname'
            ])
//            ->leftJoin(Module::tableName(). ' AS b', 'b.id = a.module_id')
            ->leftJoin(Menu::tableName().' c', 'c.id = a.parent_menu_id');

        if(!isset($params['status'])) {
            $query->where(['a.status' => [Menu::STATUS_INVALID, Menu::STATUS_ENABLE]]);
        } else {
            $query->where(['a.status' => $params['status']]);
        }

        // 搜索菜单名称
        if (isset($params['keyword']) && $params['keyword']) {
            $query->andWhere([
                'or',
                ['like','a.name',$params["keyword"]],
                ['like','a.url', $params["keyword"]]
            ]);
        }

        $order = ['a.status' => SORT_DESC, 'a.sort' => SORT_ASC, 'a.id' => SORT_ASC];

        return $this->getListPageRes($query, $page, $pageSize, $order);
    }

    /**
     * 格式化菜单列表
     * @param array $data
     * @return array
     */
    public function getListPageFormat(array $data): array
    {
        $list = [];
        foreach ($data as $row) {
            $list[] = [
                'id' => $row['id'],
                'parent_menu_id' => $row['parent_menu_id'],
                'module_id' => $row['module_id'],
                'name' => $row['name'],
                'pname' => $row['pname'] ?: '',
//                'module_name' => $row['module_name'] ?: '',
                'flag' => $row['flag'],
                'url' => $row['url'],
                'class_style' => $row['class_style'],
                'sort' => $row['sort'],
                'status' => $row['status'],
                'status_str' => Menu::$statuss[$row['status']] ?? '',
                'created_at_str' => DateHelper::timestampToDate($row['created_at']),
            ];
        }
        return $list;
    }

    /**
     * 菜单详情
     * @param array $params
     * @return array
     */
    public function getInfo(int $id): array
    {
        $data = [];
        $checkId = 0;
        // 菜单信息
        $menu = Menu::findOne($id);
        if ($menu) {
            $data = [
                'id' => $menu->id,
                'parent_menu_id' => $menu->parent_menu_id,
                'module_id' => $menu->module_id,
                'name' => $menu->name,
                'url' => $menu->url,
                'class_style' => $menu->class_style,
                'status' => $menu->status,
                'sort' => $menu->sort,
            ];
            $checkId = $menu->parent_menu_id;
        }

        return array_merge($data, [
            'condition' => [
                'menu' => $this->getConditionList($checkId),
            ],
        ]);
    }

    /**
     * 菜单筛选列表
     * @param int $checkId 选中ID
     * @return mixed
     */
    public function getConditionList(int $checkId = 0)
    {
        // 菜单列表
        $menuList = Menu::find()
            ->where(['status' => Menu::STATUS_ENABLE])
            ->asArray()
            ->all();

        return $this->getConditionListFormat($menuList, 0, $checkId);
    }

    /**
     * 格式化菜单数据
     * @param array $data 查询结果
     * @param int $pid 菜单parent_menu_id
     * @param int $checkId 选中ID
     * @return array
     */
    private function getConditionListFormat(array $data, int $pid = 0, int $checkId = 0)
    {
        $list = [];
        foreach ($data as $row) {
            if ($row['parent_menu_id'] != $pid) {
                continue;
            }

            // 是否选中
            $checked = $checkId != 0 && ($checkId == $row['id'] || $checkId == $row['parent_menu_id']) ? true : false;

            // 子菜单
            $children = $this->getConditionListFormat($data, $row['id'], $checkId);

            $item = [
                'value' => $row['id'],
                'title' => $row['name'],
                'checked' => $checked,
                'expand' => $children ? true : false,
//                'children' => $children[$row['module_id']] ?? [],
                'children' => $children,
            ];

//            $list[$row['module_id']][] = $item;
            $list[] = $item;
        }

        return $list;
    }

    /**
     * 获取全部有效状态菜单
     * @return array
     */
    public static function getEnableList(): array
    {
        return Menu::find()
            ->where(['status' => Menu::STATUS_ENABLE])
            ->orderBy(['sort' => SORT_ASC, 'id' => SORT_ASC])
            ->asArray()
            ->all();
    }

    /**
     * 根据IDs获取全部有效状态菜单
     * @param array $ids
     * @return array
     */
    public static function getEnableListByIds(array $ids): array
    {
        if (empty($ids)) {
            return [];
        }
        return Menu::find()
            ->where([
                'id' => $ids,
                'status' => Menu::STATUS_ENABLE,
            ])
            ->orderBy(['sort' => SORT_ASC, 'id' => SORT_ASC])
            ->asArray()
            ->all();
    }

    /**
     * 根据Ids获取菜单列表【禁用 + 正常】
     * @param array $menuIds
     * @return Menu[]
     */
    public static function getNormalCacheListByIds(array $menuIds): array
    {
        $data = [];
        foreach ($menuIds as $menuId) {
            $menu = RedisHelper::getMenu($menuId);
            if (empty($menu) || $menu->status == Menu::STATUS_DELETE) {
                continue;
            }
            $data[] = $menu;
        }
        return $data;
    }

    /**
     * 获取菜单权限
     * @return array
     */
    public function getInnerActionList(int $roleId): array
    {
        $auth = MemberService::getMemberLimitData(Yii::$app->user->identity->member, $roleId);

        // 当前角色绑定IDs
        $roleMenuIds = array_column(RolePermissionService::getRolePermissionListByRoleIds(RolePermission::PERMISSION_TYPE_MENU, $roleId), 'permission_id');
        $roleActionIds = array_column(RolePermissionService::getRolePermissionListByRoleIds(RolePermission::PERMISSION_TYPE_ACTION, $roleId), 'permission_id');
        // 账号拥有权限IDs
        $hasMenuIds = $hasActionIds = null;
        // 非当前用户权限IDs
        $diffMenuIds = $diffActionIds = [];
        if (Yii::$app->user->identity->member->super_type != Member::SUPER_TYPE_SUPER_ADMIN) {
            $hasMenuIds = array_column(RolePermissionService::getRolePermissionListByRoleIds(RolePermission::PERMISSION_TYPE_MENU, $auth['role_ids']), 'permission_id');
            $hasActionIds = array_column(RolePermissionService::getRolePermissionListByRoleIds(RolePermission::PERMISSION_TYPE_ACTION, $auth['role_ids']), 'permission_id');

            $diffMenuIds = array_diff($roleMenuIds, $hasMenuIds);
            $diffActionIds = array_diff($roleActionIds, $hasActionIds);
        }

        $actionFormat = [];
        foreach ($auth['action'] as $row) {
            $row['disabled'] = Yii::$app->user->identity->member->super_type == Member::SUPER_TYPE_SUPER_ADMIN
                ? 0
                : (in_array($row['id'], $diffActionIds) ? 1 : 0);
            $actionFormat[$row['menu_id']] = $actionFormat[$row['menu_id']] ?? [];
            $actionFormat[$row['menu_id']][] = $row;
        }
        $menuFormat = [];
        foreach ($auth['menu'] as $row) {
            $row['disabled'] = Yii::$app->user->identity->member->super_type == Member::SUPER_TYPE_SUPER_ADMIN
                ? 0
                : (in_array($row['id'], $diffMenuIds) ? 1 : 0);
            $row['action_list'] = $actionFormat[$row['id']] ?? [];
            $menuFormat[] = $row;
        }
        return $menuFormat;
    }

    /**
     * 格式化菜单权限
     * @param array $data
     * @param int $pid
     * @param array $hasAuth 已有权限 [
     *      'menu' => [1,2,3...],// 菜单IDs
     *      'action' => [1,2,3...]// 动作IDs
     * ]
     * @return array
     */
    public function getInnerActionListFormat(array $data = [], $pid = 0, array $hasAuth = [], int $level = 1): array
    {
        // 已拥有权限
        $menuAuth = $hasAuth['menu'] ?? [];
        $actionAuth = $hasAuth['action'] ?? [];

        $list = [];
        foreach ($data as $row) {
            if ($row['parent_menu_id'] != $pid) {
                continue;
            }
            // 子菜单
            $children = $this->getInnerActionListFormat($data, $row['id'], $hasAuth, $level+1);
            // 动作列表
            foreach ($row['action_list'] as $action) {
                $children[] = [
                    'value' => $action['id'],
                    'title' => $action['name'],
                    'pid' => $row['id'],
                    'level' => $level+1,
                    'permission_type' => RolePermission::PERMISSION_TYPE_ACTION,
                    'expand' => false,
                    'checked' => in_array($action['id'], $actionAuth),
                    'half_checked' => false,
                    'disabled' => $action['disabled'],
                ];
            }

            // 当前菜单
            $checked = in_array($row['id'], $menuAuth);
            $checkData = $this->getChildCheckStatus($children, $checked);
            $item = [
                'value' => $row['id'],
                'title' => $row['name'],
                'pid' => $row['parent_menu_id'] ?: $row['module_id'],
                'level' => $level,
                'permission_type' => RolePermission::PERMISSION_TYPE_MENU,
                'expand' => $children ? true : false,
                'checked' => $checkData['checked'],
                'half_checked' => $checkData['half_checked'],
                'children' => $children,
                'disabled' => $row['disabled'],
            ];
            $list[] = $item;
        }

        return $list;
    }

    /**
     * 获取选中状态
     * @param array $data
     * @param bool $checkItem 选中权限
     * @return bool[]
     */
    public function getChildCheckStatus(array $data, bool $checkItem): array
    {
        $check = $checkItem;
        $halfCheck = false;
        foreach ($data as $row) {
            if (!$check && $halfCheck) {
                break;
            }
            if ($row['checked'] || $row['half_checked']) {
                $halfCheck = true;
            }
            if (!$row['checked']) {
                $check = false;
            }
        }

        return [
            'checked' => $check,// 选中
            'half_checked' => $check ? false : $halfCheck,// 半选
        ];
    }

    /**
     * 编辑菜单
     * @param Menu $menu
     * @param MenuForm $model
     * @return Menu
     * @throws \Exception
     */
    public static function editModel(Menu $menu, MenuForm $model): Menu
    {
        $menu->parent_menu_id = $model->parent_menu_id;
//        $menu->module_id = $model->module_id;
        $menu->name = $model->name;
        $menu->url = $model->url;
        $menu->class_style = $model->class_style;
        $menu->status = $model->status;
        $menu->sort = $model->sort;
        $menu->saveAndCheckResult();

        return $menu;
    }

    /**
     * 新增菜单
     * @param MenuForm $model
     * @return array
     */
    public function create(MenuForm $model): array
    {
        try {
            $transaction = Yii::$app->db->beginTransaction();

            // 1.新增菜单
            $menu = new Menu();
            $menu = self::editModel($menu, $model);

            // 2.日志
            LogMemberOpService::createMenuLog($model, $menu);

            $transaction->commit();
            return ['id' => $menu->id];
        } catch (\Exception $e) {
            $transaction->rollBack();
            throw $e;
        }
    }

    /**
     * 编辑菜单
     * @param MenuForm $model
     * @return array
     */
    public function edit(MenuForm $model): array
    {
        try {
            $transaction = Yii::$app->db->beginTransaction();

            // 1.更新菜单
            $oldMenu = new Menu();
            $oldMenu->setAttributes($model->getMenu()->getAttributes());
            $menu = self::editModel($model->getMenu(), $model);

            // 2.日志
            LogMemberOpService::createMenuLog($model, $menu, $oldMenu);

            // 3.更新缓存
            RedisHelper::setMenu($menu);

            $transaction->commit();
            return ['id' => $menu->id];
        } catch (\Exception $e) {
            $transaction->rollBack();
            throw $e;
        }
    }

    /**
     * 删除菜单
     * @param MenuForm $model
     * @return array
     */
    public function delete(MenuForm $model): array
    {
        try {
            $transaction = Yii::$app->db->beginTransaction();

            // 1.更新菜单
            $menu = $model->getMenu();
            $menu->status = Menu::STATUS_DELETE;
            $menu->saveAndCheckResult();
            // 2.删除菜单绑定动作
            $actionIds = ActionService::deleteMenuAction($menu->id);
            // 3.删除角色绑定菜单、动作
            $menuRoleIds = RolePermissionService::deleteMenuPermission($menu->id);
            $actionRoleIds = RolePermissionService::deleteActionPermission($actionIds);
            // 3-2.绑定角色人员强制退出登录
            MemberRoleService::clearRoleMemberToken(array_merge($menuRoleIds, $actionRoleIds));
            // 4.日志
            LogMemberOpService::createMenuLog($model, $menu);
            // 5.更新缓存
            RedisHelper::setMenu($menu);

            $transaction->commit();
            return ['id' => $menu->id];
        } catch (\Exception $e) {
            $transaction->rollBack();
            throw $e;
        }
    }

    /**
     * 编辑菜单状态
     * @param MenuForm $model
     * @return array
     */
//    public function editStatus(MenuForm $model)
//    {
//        try {
//            // 1.更新菜单
//            $menu = $model->getMenu();
//            $menu->status = $model->status;
//            $menu->saveAndCheckResult();
//
//            return ['id' => $menu->id];
//        } catch (\Exception $e) {
//            return CommonHelper::api(422, $e->getMessage());
//        }
//    }
}
